
package hasthi.decision;

import static hasthi.common.constants.HasthiConstants.LOGGER_NAME;
import hasthi.actuators.ActionCenter;
import hasthi.actuators.ActionContext;
import hasthi.common.HasthiException;
import hasthi.common.HasthiException.FaultCode;
import hasthi.metamodel.MixedResourceModel;
import hasthi.metamodel.ResourceModel;
import hasthi.metamodel.SummerizedResourceModel;
import hasthi.metamodel.resource.EditableManagedResource;
import hasthi.metamodel.resource.ManagedResource;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.drools.FactException;
import org.drools.FactHandle;
import org.drools.QueryResult;
import org.drools.QueryResults;
import org.drools.RuleBase;
import org.drools.RuleBaseFactory;
import org.drools.StatefulSession;
import org.drools.compiler.DroolsParserException;
import org.drools.compiler.PackageBuilder;
import org.drools.compiler.PackageBuilderConfiguration;
import org.drools.event.ObjectInsertedEvent;
import org.drools.event.ObjectRetractedEvent;
import org.drools.event.ObjectUpdatedEvent;
import org.drools.event.WorkingMemoryEventListener;

public class RuleEngine{
    private static Log log = LogFactory.getLog(LOGGER_NAME);
    private final RuleBase ruleBase;
    private StatefulSession session;
    private final FactHandle envHanlde;
    private final Enviorment env;
    private final ActionCenter actionCenter;
    
    private final FactHandle signalbufferHanlde;
    private final Vector<Signal> signalbuffer = new Vector<Signal>();
    private final Vector<Signal> fixedSignalBuffer = new Vector<Signal>();
    private final Vector<Object> newObjects = new Vector<Object>();
    private final Vector<Object> changedObjects = new Vector<Object>();
    private final Vector<Object> removedObjects = new Vector<Object>();
    
    //private final Map<Object,FactHandle> facthandleMap = new Hashtable<Object, FactHandle>();
    
    public RuleEngine(String rulefile) throws HasthiException{
        this(rulefile,null);
    }
    
    public RuleEngine(String rulefileAsStr, ActionCenter actionCenter)throws HasthiException{
        try {
            //RuleBaseConfiguration configuration = new RuleBaseConfiguration();
            //add the package to a rulebase
            ruleBase = RuleBaseFactory.newRuleBase();
            if(rulefileAsStr !=  null){
                Reader source = null;
                File ruleFile = new File(rulefileAsStr);
                if(ruleFile.exists()){
                    source = new FileReader(ruleFile);
                }else{
                    InputStream ruleStream = Thread.currentThread()
                        .getContextClassLoader().getResourceAsStream(rulefileAsStr);
                    if(ruleStream != null){
                        source = new InputStreamReader(ruleStream);    
                    }else{
                        throw new HasthiException("Rule file "+ rulefileAsStr + " not found",FaultCode.ConfigError);
                    }
                }
                if(source != null){
                    PackageBuilder builder = new PackageBuilder();
                    builder.addPackageFromDrl(source);
                    org.drools.rule.Package pkg = builder.getPackage();
                    ruleBase.addPackage( pkg );
                    source.close();
                }
            }
            session = ruleBase.newStatefulSession();
            session.setGlobal("system", actionCenter);
            session.addEventListener(new WorkingMemoeryListener());
            env = new Enviorment();
            envHanlde = realAddObject(env);
            this.actionCenter = actionCenter;
//            if(actionCenter != null){
//                addObject(this.actionCenter);    
//            }
            signalbufferHanlde = realAddObject(fixedSignalBuffer);
            //fire();
        } catch (DroolsParserException e) {
            throw new RulebaseException(e,FaultCode.InitalizationError);
        } catch (IOException e) {
            throw new RulebaseException(e,FaultCode.InitalizationError);
        } catch (Exception e) {
            throw new RulebaseException(e,FaultCode.InitalizationError);
        }
    }
    
    public void resetSession() throws HasthiException{
        session.dispose();
        session = ruleBase.newStatefulSession();
        if(actionCenter != null){
            addObject(actionCenter);    
        }
    }

    public void addSignal(Signal signal){
        synchronized (signalbuffer) {
            signalbuffer.add(signal);    
        }
    }

    public void addObject(Object object) throws HasthiException{
        if(object == null){
            throw new HasthiException("Object must not be Null",FaultCode.InvaliedLocalArgumnet);
        }
        synchronized (newObjects) {
            newObjects.add(object);
        }
    }
    
    
    
    public void removeObject(Object object) throws HasthiException{
        if(object == null){
            throw new HasthiException("Object must not be Null",FaultCode.InvaliedLocalArgumnet);
        }
        synchronized (removedObjects) {
            removedObjects.add(object);
        }
    }
    
    public void objectChanged(Object obj) throws HasthiException{
        if(obj == null){
            throw new HasthiException("Object must not be Null",FaultCode.InvaliedLocalArgumnet);
        }
        synchronized (changedObjects) {
            changedObjects.add(obj);
        }
    }
    
    public FactHandle realAddObject(Object object) throws HasthiException{
        Object objectToAdd = object;
        FactHandle factHandle = session.insert(objectToAdd);
        //facthandleMap.put(object, factHandle);
        return factHandle;
    }
    
    
    
    public void realRemoveObject(Object object) throws HasthiException{
        FactHandle factHandle = null;
        try {
            //System.out.println("removed "+object.toString());
            //FactHandle factHandle = facthandleMap.get(object);
            factHandle = session.getFactHandle(object);
            session.retract(factHandle);
            //facthandleMap.remove(object);
        } catch (Throwable e) {
            throw new HasthiException("Failed trying to remove "+ object +  " fact handle was "+ factHandle,e,FaultCode.LocalError);
        }
    }
    
    public void realObjectChanged(Object obj){
        FactHandle factHandle = session.getFactHandle(obj);
        //FactHandle factHandle = facthandleMap.get(obj);
        if(factHandle != null){
            session.update(factHandle, obj);    
        }else{
            System.out.println("Update on removed Object "+obj);
        }
        
        //System.out.println("Object Changed"+obj);
    }

    
    
    public void fire() throws HasthiException{
        //To make sure all changes happens in same thread,
        //we buffer the changes and perfrom them before fire
        if(newObjects.size() > 0){
            synchronized (newObjects) {
                for(Object object:newObjects){
                    try{
                        realAddObject(object);
                    }catch(Throwable e){
                        log.error("Error at rule engine fire "+e.getMessage(),e);
                    }
                }
                newObjects.clear();
            }
        }
        if(changedObjects.size() > 0){
            synchronized (changedObjects) {
                for(Object object:changedObjects){
                    try{
                        realObjectChanged(object);
                    }catch(Throwable e){
                        log.error("Error at rule engine fire "+e.getMessage(),e);
                    }
                }
                changedObjects.clear();
            }
        }
        if(removedObjects.size() > 0){
            synchronized (removedObjects) {
                for(Object object:removedObjects){
                    try{
                        realRemoveObject(object);
                    }catch(Throwable e){
                        log.error("Error at rule engine fire " +e.getMessage(),e);
                    }
                }
                removedObjects.clear();
            }
        }
        if(signalbuffer.size() > 0){
            synchronized (signalbuffer) {
                fixedSignalBuffer.clear();
                fixedSignalBuffer.addAll(signalbuffer);
                signalbuffer.clear();
            }
            session.update(signalbufferHanlde, fixedSignalBuffer);
        }
        //TODO support signals
        session.update(envHanlde, env);
        session.fireAllRules();
        actionCenter.planAndExecuate();
    }
    
    
    public QueryResult[] execuateQuery(String queryName,String[] arguments){
        QueryResults queryResults = session.getQueryResults(queryName, arguments);
        QueryResult[] finalResults = new QueryResult[queryResults.size()];
        int i = 0;
        for(Iterator it = queryResults.iterator();it.hasNext();){
            finalResults[i] = (QueryResult)it.next();
            i++;
        }
        return finalResults;
    }
    
    
    public List<ManagedResource> queryManagedResources(String queryName,String[] arguments){
        QueryResult[] results = execuateQuery(queryName, arguments);
        List<ManagedResource> managedResources = new ArrayList<ManagedResource>();
        for(QueryResult result:results){
            for(int i = 0;i<result.size();i++){
                managedResources.add((ManagedResource)result.get(i));    
            }
        }
        return managedResources;
    }
    
    

    public void addRule(Reader source)throws Exception{
        PackageBuilder builder = new PackageBuilder();
        builder.addPackageFromDrl(source);
        org.drools.rule.Package pkg = builder.getPackage();
        
        this.ruleBase.addPackage(pkg);
        
        
    }
    
    public void removeRule(String packageName, String rule){
        this.ruleBase.removeRule(packageName, rule);
    }
    

    public ActionCenter getManagerRingProxy() {
        return actionCenter;
    }
    
    public class WorkingMemoeryListener implements WorkingMemoryEventListener{
        public void objectInserted(ObjectInsertedEvent event) {
        }
        public void objectRetracted(ObjectRetractedEvent event) {
            Object obj =event.getWorkingMemory().getObject(event.getFactHandle());
            try {
                if(obj instanceof EditableManagedResource){
                    EditableManagedResource managedResource = (EditableManagedResource)obj;
                    ActionContext actionContext = actionCenter.getActionContext();
                    if(actionContext != null){
                        if(actionContext.isCoordinator()){
                            SummerizedResourceModel resourceModel = (SummerizedResourceModel)actionContext.getResourceModel();
                            resourceModel.removeResource(managedResource.getQName());
                            actionContext.getCoordinatorContext().removeResource(managedResource.getQName());
                        }else{
                            MixedResourceModel resourceModel = (MixedResourceModel)actionContext.getResourceModel();
                            resourceModel.removeResource(managedResource.getQName());
                        }
                    }
                    log.info("Removed "+managedResource.getQName() + " from the system");
                }
            } catch (HasthiException e) {
                //e.printStackTrace();
            }
        }
        public void objectUpdated(ObjectUpdatedEvent event) {
        }
    }

    public ActionCenter getActionCenter() {
        return actionCenter;
    }
    
}

